Simple Classifier¶

Introduction VQA¶

The-workflow-of-a-typical-variational-quantum-algorithm.png

Source Xiong, Ng, and Hanzo 2021

Ansatz¶

Expressivity_ansatz.png

Source Nakaji & Yamamoto, 2020

Optimization Part¶

Gradient_descent.png

Optimizer qui sont les plus utilisés en Deep Learning (source analyticsvidhya a-comprehensive-guide-on-deep-learning-optimizer)

121381obtV.gif

56201contours_evaluation_optimizers.gif

Parity Function¶

Toy example sur classification de données en chaines binaires mappées sur un label/étiquette (0011 -> 1).

In [2]:
import pennylane as qml
from pennylane import numpy as np
from pennylane.optimize import NesterovMomentumOptimizer

Création du simulator avec device avec 4 qubits en utilisant le simulateur lightning.qubit.

In [3]:
dev = qml.device("lightning.qubit", wires=4)

Blocs fondamentaux¶

Création d'une couche ou bloc pour le circuit paramétrisé qui sera utilisé pour générer l'algorithme variationnel.

In [1]:
def layer(layer_weights):
    '''
    Function to build simple layer with rotations gates and cnot. Basis element of a parametrized circuit. 
    Args: 
        layer_weights: weights/angles to use for the rotations
    Return: 
        Parametrized layer
    '''
    # Rotations in the 3 axes
    
    for wire in range(4):
        qml.Rot(*layer_weights[wire], wires=wire)

    for wires in ([0, 1], [1, 2], [2, 3], [3, 0]):
        qml.CNOT(wires)

Création de la fonction pour encoder les données classique dans un état quantique. BasisState permet d'encoder une chaine de 0s et de 1s (bitstring) dans la base de calcul: $$ x = 0101 \ \rightarrow \ \vert \psi\rangle = \vert 0101 \rangle $$

In [4]:
def statepreparation(x):
    '''
    Basis encoding for an arbitrary x vector 
    Args:
        x: vector of dimension 4
    Return: 
        Layer phi with X gates to encode the vector x 
    '''
    qml.BasisState(x, wires=[0, 1, 2, 3])
    

Création du cricuit paramétrisé. Le qnode permet de mapper le circuit sur le simulateur choisi au départ. Le paramètre interface est utilisé pour calculer la backpropagation classique dans l'algorithme variationnel. Le paramètre weights permet de déterminer le nombre de couche dans le circuit paramétrisé déterminé par la fonction layer.

In [5]:
@qml.qnode(dev, interface="autograd")
def circuit(weights, x):
    '''
    Parametrized circuit with data encoding (statepreparation) and layer repetition based on the weights 
    Args:
        weights: angles for the rotations (num layer, num qubits, num directions)
        x: input vector
    Return: 
        Expectation values measured on Pauli Z operators for the state 0
    '''
    # data encoding 
    statepreparation(x)

    # ansatz 
    for W in weights:
        layer(W)

    # measure
    return qml.expval(qml.PauliZ(0))

Construction du cricuit pour l'algorithme variationnel, on ajoute un terme biais pour plus de liberté dans l'apprentissage.

In [6]:
def variational_classifier(weights, bias, x):
    '''
    Build the parametrized circuit with weights, x and bias term
    Args:
        - weights: rotation angles 
        - bias: classical term to add more freedom to the VQA
        - x: input vector/data 
    Returns: 
        - parametrized circuit with a bias term 
    '''
    return circuit(weights, x) + bias

Cost¶

La seconde étape est de pouvoir construire les métriques et outils pour diriger l'apprentissage. Dans l'apprentissage supervisé, la fonction de coût est généralement la somme d'une fonction de perte et d'un régularisateur. Nous nous limitons à la différence au carrée entre la valeur prédite et la valeur de référence. La fonction de coût mesure la distance (erreur) entre les étiquettes cibles et les prédictions du modèle.

In [7]:
def square_loss(labels, predictions):
    '''
    Compute the cost function
    Args:
        - labels: Ground truth
        - predictions: Predicted values 
    Returns: 
        - Mean of the square error between labels and predictions = model's error 
    '''
    
    # We use a call to qml.math.stack to allow subtracting the arrays directly
    return np.mean((labels - qml.math.stack(predictions)) ** 2)

Pour savoir combien d'entrées le classificateur actuel a correctement prédit, on définit également la précision, ou la proportion de prédictions qui correspondent à un ensemble d'étiquettes cibles. L'accuracy est une métrique qui se calcule comme: $$ \frac{TP+TN}{TP+TN+FP+FN} = \frac{Bonnes \ prédictions}{Toutes \ les \ prédictions} $$

Cette métrique est vivement déconseillée dans les cas ou les données sont débalancées (la classe minoritaire ayant un volume très faible comparé à la classe majoritaire.

In [8]:
def accuracy(labels, predictions):
    '''
    Compute the accuracy of the model
    Args:
        - labels: Ground truth
        - predictions: Predicted values 
    Returns: 
        - accuracy
    '''
    acc = sum(abs(l - p) < 1e-5 for l, p in zip(labels, predictions))
    acc = acc / len(labels)
    return acc

Maintenant, il est temps de créer la fonction costqui va regrouper les parties précédentes pour calculer l'erreur des prédictions du modèle.

In [9]:
def cost(weights, bias, X, Y):
    '''
    Compute the cost of the model
    Args: 
        - weights: rotation angles 
        - bias: classical term to add more freedom to the VQA
        - X: input vector/data 
        - Y: True labels 
    Returns: 
        - Error prediction / distance 
    '''
    predictions = [variational_classifier(weights, bias, x) for x in X]
    return square_loss(Y, predictions)

Optimisation¶

Récupération des données et split entre les données et les labels.

In [10]:
data = np.loadtxt("data/parity.txt")
X = np.array(data[:, :-1], requires_grad=False)
Y = np.array(data[:, -1], requires_grad=False)
Y = Y * 2 - 1  # shift label from {0, 1} to {-1, 1}

for x,y in zip(X, Y):
    print(f"x = {x}, y = {y}")
x = [0. 0. 0. 0.], y = -1.0
x = [0. 0. 0. 1.], y = 1.0
x = [0. 0. 1. 0.], y = 1.0
x = [0. 0. 1. 1.], y = -1.0
x = [0. 1. 0. 0.], y = 1.0
x = [0. 1. 0. 1.], y = -1.0
x = [0. 1. 1. 0.], y = -1.0
x = [0. 1. 1. 1.], y = 1.0
x = [1. 0. 0. 0.], y = 1.0
x = [1. 0. 0. 1.], y = -1.0
x = [1. 0. 1. 0.], y = -1.0
x = [1. 0. 1. 1.], y = 1.0
x = [1. 1. 0. 0.], y = -1.0
x = [1. 1. 0. 1.], y = 1.0
x = [1. 1. 1. 0.], y = 1.0
x = [1. 1. 1. 1.], y = -1.0
In [11]:
np.random.seed(0)
num_qubits = 4
num_layers = 2
weights_init = 0.01 * np.random.randn(num_layers, num_qubits, 3, requires_grad=True)
bias_init = np.array(0.0, requires_grad=True)

print(weights_init, bias_init)
[[[ 0.01764052  0.00400157  0.00978738]
  [ 0.02240893  0.01867558 -0.00977278]
  [ 0.00950088 -0.00151357 -0.00103219]
  [ 0.00410599  0.00144044  0.01454274]]

 [[ 0.00761038  0.00121675  0.00443863]
  [ 0.00333674  0.01494079 -0.00205158]
  [ 0.00313068 -0.00854096 -0.0255299 ]
  [ 0.00653619  0.00864436 -0.00742165]]] 0.0
In [12]:
np.shape(weights_init)
Out[12]:
(2, 4, 3)
In [13]:
fig, ax = qml.draw_mpl(circuit, style="sketch", decimals=3)(weights_init, X)
fig.show()
/var/folders/__/yb0d9fls4r9gb8t6yccwhq_w0000gn/T/ipykernel_6811/3649643044.py:2: UserWarning: Matplotlib is currently using module://matplotlib_inline.backend_inline, which is a non-GUI backend, so cannot show the figure.
  fig.show()
In [14]:
fig, ax = qml.draw_mpl(variational_classifier, style="sketch", decimals=3, expansion_strategy='device')(weights_init, bias_init, X[0])
fig.show()
/Users/christophe_pere/PinQ2_py/lib/python3.9/site-packages/pennylane/drawer/draw.py:536: UserWarning: When the input to qml.draw is not a QNode, the expansion_strategy argument is ignored.
  warnings.warn(
/var/folders/__/yb0d9fls4r9gb8t6yccwhq_w0000gn/T/ipykernel_6811/473859013.py:2: UserWarning: Matplotlib is currently using module://matplotlib_inline.backend_inline, which is a non-GUI backend, so cannot show the figure.
  fig.show()

Optimizer¶

In [15]:
opt = NesterovMomentumOptimizer(0.5)
batch_size = 5

On va exécuter l'optimiseur pour entraîner notre modèle. On utilise la fonction accuracy pour estimer la part de bonnes prédictions. Pour cela, nous calculons les sorties du classificateur variationnel et les transformons en prédictions en { - 1 , 1 } en prenant le signe de la sortie.

In [16]:
weights = weights_init
bias = bias_init
for it in range(25):

    # Update the weights by one optimizer step
    batch_index = np.random.randint(0, len(X), (batch_size,))
    X_batch = X[batch_index]
    Y_batch = Y[batch_index]
    weights, bias, _, _ = opt.step(cost, weights, bias, X_batch, Y_batch)

    # Compute accuracy
    predictions = [np.sign(variational_classifier(weights, bias, x)) for x in X]
    acc = accuracy(Y, predictions)

    print(
        "Iter: {:5d} | Cost: {:0.7f} | Accuracy: {:0.7f} ".format(
            it + 1, cost(weights, bias, X, Y), acc
        )
    )
Iter:     1 | Cost: 3.4355534 | Accuracy: 0.5000000 
Iter:     2 | Cost: 1.9287800 | Accuracy: 0.5000000 
Iter:     3 | Cost: 2.0341238 | Accuracy: 0.5000000 
Iter:     4 | Cost: 1.6372574 | Accuracy: 0.5000000 
Iter:     5 | Cost: 1.3025395 | Accuracy: 0.6250000 
Iter:     6 | Cost: 1.4555019 | Accuracy: 0.3750000 
Iter:     7 | Cost: 1.4492786 | Accuracy: 0.5000000 
Iter:     8 | Cost: 0.6510286 | Accuracy: 0.8750000 
Iter:     9 | Cost: 0.0566074 | Accuracy: 1.0000000 
Iter:    10 | Cost: 0.0053045 | Accuracy: 1.0000000 
Iter:    11 | Cost: 0.0809483 | Accuracy: 1.0000000 
Iter:    12 | Cost: 0.1115426 | Accuracy: 1.0000000 
Iter:    13 | Cost: 0.1460257 | Accuracy: 1.0000000 
Iter:    14 | Cost: 0.0877037 | Accuracy: 1.0000000 
Iter:    15 | Cost: 0.0361311 | Accuracy: 1.0000000 
Iter:    16 | Cost: 0.0040937 | Accuracy: 1.0000000 
Iter:    17 | Cost: 0.0004899 | Accuracy: 1.0000000 
Iter:    18 | Cost: 0.0005290 | Accuracy: 1.0000000 
Iter:    19 | Cost: 0.0024304 | Accuracy: 1.0000000 
Iter:    20 | Cost: 0.0062137 | Accuracy: 1.0000000 
Iter:    21 | Cost: 0.0088864 | Accuracy: 1.0000000 
Iter:    22 | Cost: 0.0201912 | Accuracy: 1.0000000 
Iter:    23 | Cost: 0.0060335 | Accuracy: 1.0000000 
Iter:    24 | Cost: 0.0036153 | Accuracy: 1.0000000 
Iter:    25 | Cost: 0.0012741 | Accuracy: 1.0000000 

Comme nous pouvons le voir, le classificateur variationnel a appris à classer correctement toutes les chaînes de bits de l'ensemble d'apprentissage.

Mais contrairement à l'optimisation, dans l'apprentissage automatique, l'objectif est de généraliser des données limitées à des exemples non vus. Même si le circuit quantique variationnel était parfaitement optimisé en termes de coût, il pourrait ne pas se généraliser, un phénomène connu sous le nom d'overfitting. L'art de l'apprentissage automatique (quantique) consiste à créer des modèles et des procédures d'apprentissage qui tendent à trouver de "bons" minima, ou qui conduisent à des modèles qui se généralisent bien.

Generalisation?¶

In [21]:
data = np.loadtxt("data/parity_test.txt", dtype=int)
X_test = np.array(data[:, :-1])
Y_test = np.array(data[:, -1])
Y_test = Y_test * 2 - 1  # shift label from {0, 1} to {-1, 1}

predictions_test = [np.sign(variational_classifier(weights, bias, x)) for x in X_test]

for x,y,p in zip(X_test, Y_test, predictions_test):
    print(f"x = {x}, y = {y}, pred={p}")

acc_test = accuracy(Y_test, predictions_test)
print("\nAccuracy on unseen data:", acc_test)
x = [0 0 0 0], y = -1, pred=-1.0
x = [0 0 1 1], y = -1, pred=-1.0
x = [1 0 1 0], y = -1, pred=-1.0
x = [1 1 1 0], y = 1, pred=1.0
x = [1 1 0 0], y = -1, pred=-1.0
x = [1 1 0 1], y = 1, pred=1.0

Accuracy on unseen data: 1.0

Le circuit quantique a également appris à prédire parfaitement tous les exemples non vus ! C'est en fait remarquable, car la stratégie d'encodage crée des états quantiques à partir des données qui n'ont aucun chevauchement - et donc les états créés à partir de l'ensemble de test n'ont aucun chevauchement avec les états créés à partir de l'ensemble d'apprentissage. Le circuit variationnel pourrait apprendre de nombreuses relations fonctionnelles à partir de ce type de représentation, mais le classificateur choisit d'étiqueter les chaînes de bits en fonction de notre vérité de base, la fonction de parité.


Iris dataset¶

Préparons un simulateur avec 2 qubits pour les deux labels.

In [26]:
dev = qml.device("lightning.qubit", wires=2)

La préparation de l'état n'est pas aussi simple que lorsque nous représentons une chaîne de bits par un état de base. Chaque entrée x doit être traduite en un ensemble d'angles qui peuvent être introduits dans une petite routine de préparation d'état. Pour simplifier un peu les choses, nous travaillerons avec des données provenant du sous-espace positif, de sorte que nous pourrons ignorer les signes (ce qui nécessiterait une autre cascade de rotations autour de l'axe Z).

In [27]:
def get_angles(x):
    '''
    Transform data as angles
    Args:
        - x: input vector of iris data
    Return:
        - Angle encoding data
    '''
    beta0 = 2 * np.arcsin(np.sqrt(x[1] ** 2) / np.sqrt(x[0] ** 2 + x[1] ** 2 + 1e-12))
    beta1 = 2 * np.arcsin(np.sqrt(x[3] ** 2) / np.sqrt(x[2] ** 2 + x[3] ** 2 + 1e-12))
    beta2 = 2 * np.arcsin(
        np.sqrt(x[2] ** 2 + x[3] ** 2)
        / np.sqrt(x[0] ** 2 + x[1] ** 2 + x[2] ** 2 + x[3] ** 2)
    )

    return np.array([beta2, -beta1 / 2, beta1 / 2, -beta0 / 2, beta0 / 2])


def statepreparation(a):
    '''
    Prepare the state according to the corresponding get_angles transformation. 
    Args: 
        - a: angles from get_angles transformation
    Return: 
        - Parametrized circuit with the corresponding angles 
    '''
    qml.RY(a[0], wires=0)
    qml.CNOT(wires=[0, 1])
    qml.RY(a[1], wires=1)
    qml.CNOT(wires=[0, 1])
    qml.RY(a[2], wires=1)
    
    qml.PauliX(wires=0)
    qml.CNOT(wires=[0, 1])
    qml.RY(a[3], wires=1)
    qml.CNOT(wires=[0, 1])
    qml.RY(a[4], wires=1)
    qml.PauliX(wires=0)

Test

In [28]:
x = np.array([0.53896774, 0.79503606, 0.27826503, 0.0], requires_grad=False)
ang = get_angles(x)


@qml.qnode(dev, interface="autograd")
def test(angles):

    statepreparation(angles)

    return qml.state()


state = test(ang)

print("x               : ", x)
print("angles          : ", ang)
print("amplitude vector: ", np.real(state))
x               :  [0.53896774 0.79503606 0.27826503 0.        ]
angles          :  [ 0.56397465 -0.          0.         -0.97504604  0.97504604]
amplitude vector:  [ 5.38967743e-01  0.00000000e+00  0.00000000e+00  0.00000000e+00
  7.95036065e-01  0.00000000e+00  0.00000000e+00  0.00000000e+00
  2.78265032e-01  0.00000000e+00  0.00000000e+00  0.00000000e+00
 -2.77555756e-17  0.00000000e+00  0.00000000e+00  0.00000000e+00]
In [29]:
def layer(layer_weights):
    for wire in range(2):
        qml.Rot(*layer_weights[wire], wires=wire)
    qml.CNOT(wires=[0, 1])
In [30]:
@qml.qnode(dev, interface="autograd")
def circuit(weights, angles):
    statepreparation(angles)

    for W in weights:
        layer(W)

    return qml.expval(qml.PauliZ(0))


def variational_classifier(weights, bias, angles):
    return circuit(weights, angles) + bias


def cost(weights, bias, X, Y):
    # Transpose the batch of input data in order to make the indexing
    # in state_preparation work
    predictions = variational_classifier(weights, bias, X.T)
    return square_loss(Y, predictions)
In [31]:
data = np.loadtxt("data/iris_classes1and2_scaled.txt")
X = data[:, 0:2]
print("First X sample (original)  :", X[0])

# pad the vectors to size 2^2 with constant values
padding = 0.3 * np.ones((len(X), 1))
X_pad = np.c_[np.c_[X, padding], np.zeros((len(X), 1))]
print("First X sample (padded)    :", X_pad[0])

# normalize each input
normalization = np.sqrt(np.sum(X_pad ** 2, -1))
X_norm = (X_pad.T / normalization).T
print("First X sample (normalized):", X_norm[0])

# angles for state preparation are new features
features = np.array([get_angles(x) for x in X_norm], requires_grad=False)
print("First features sample      :", features[0])

Y = data[:, -1]
First X sample (original)  : [0.4  0.75]
First X sample (padded)    : [0.4  0.75 0.3  0.  ]
First X sample (normalized): [0.44376016 0.83205029 0.33282012 0.        ]
First features sample      : [ 0.67858523 -0.          0.         -1.080839    1.080839  ]
In [32]:
import matplotlib.pyplot as plt

plt.figure()
plt.scatter(X[:, 0][Y == 1], X[:, 1][Y == 1], c="b", marker="o", edgecolors="k")
plt.scatter(X[:, 0][Y == -1], X[:, 1][Y == -1], c="r", marker="o", edgecolors="k")
plt.title("Original data")
plt.savefig('original_data.png')
plt.show()

plt.figure()
dim1 = 0
dim2 = 1
plt.scatter(
    X_norm[:, dim1][Y == 1], X_norm[:, dim2][Y == 1], c="b", marker="o", edgecolors="k"
)
plt.scatter(
    X_norm[:, dim1][Y == -1], X_norm[:, dim2][Y == -1], c="r", marker="o", edgecolors="k"
)
plt.title("Padded and normalised data (dims {} and {})".format(dim1, dim2))
plt.savefig('normalised_data.png')
plt.show()

plt.figure()
dim1 = 0
dim2 = 3
plt.scatter(
    features[:, dim1][Y == 1], features[:, dim2][Y == 1], c="b", marker="o", edgecolors="k"
)
plt.scatter(
    features[:, dim1][Y == -1], features[:, dim2][Y == -1], c="r", marker="o", edgecolors="k"
)
plt.title("Feature vectors (dims {} and {})".format(dim1, dim2))
plt.savefig('feature_vectors.png')
plt.show()
In [33]:
fig, ax = qml.draw_mpl(statepreparation, style="sketch", decimals=3)([np.pi/2,np.pi/2,np.pi/2,np.pi/2,np.pi/2])
fig.savefig('variationao_circuit.png')
fig.show()
/var/folders/__/yb0d9fls4r9gb8t6yccwhq_w0000gn/T/ipykernel_6811/1669354998.py:3: UserWarning: Matplotlib is currently using module://matplotlib_inline.backend_inline, which is a non-GUI backend, so cannot show the figure.
  fig.show()
In [34]:
np.random.seed(0)
num_data = len(Y)
num_train = int(0.75 * num_data)
index = np.random.permutation(range(num_data))
feats_train = features[index[:num_train]]
Y_train = Y[index[:num_train]]
feats_val = features[index[num_train:]]
Y_val = Y[index[num_train:]]

# We need these later for plotting
X_train = X[index[:num_train]]
X_val = X[index[num_train:]]
In [35]:
num_qubits = 2
num_layers = 6

weights_init = 0.01 * np.random.randn(num_layers, num_qubits, 3, requires_grad=True)
bias_init = np.array(0.0, requires_grad=True)
In [36]:
fig, ax = qml.draw_mpl(circuit, style="sketch", decimals=3)(weights_init, X)
fig.show()
/var/folders/__/yb0d9fls4r9gb8t6yccwhq_w0000gn/T/ipykernel_6811/3649643044.py:2: UserWarning: Matplotlib is currently using module://matplotlib_inline.backend_inline, which is a non-GUI backend, so cannot show the figure.
  fig.show()
In [37]:
opt = NesterovMomentumOptimizer(0.01)
batch_size = 5

# train the variational classifier
weights = weights_init
bias = bias_init
for it in range(60):

    # Update the weights by one optimizer step
    batch_index = np.random.randint(0, num_train, (batch_size,))
    feats_train_batch = feats_train[batch_index]
    Y_train_batch = Y_train[batch_index]
    weights, bias, _, _ = opt.step(cost, weights, bias, feats_train_batch, Y_train_batch)

    # Compute predictions on train and validation set
    predictions_train = [np.sign(variational_classifier(weights, bias, f)) for f in feats_train]
    predictions_val = [np.sign(variational_classifier(weights, bias, f)) for f in feats_val]

    # Compute accuracy on train and validation set
    acc_train = accuracy(Y_train, predictions_train)
    acc_val = accuracy(Y_val, predictions_val)

    print(
        "Iter: {:5d} | Cost: {:0.7f} | Acc train: {:0.7f} | Acc validation: {:0.7f} "
        "".format(it + 1, cost(weights, bias, features, Y), acc_train, acc_val)
    )
Iter:     1 | Cost: 1.4490948 | Acc train: 0.4933333 | Acc validation: 0.5600000 
Iter:     2 | Cost: 1.3312057 | Acc train: 0.4933333 | Acc validation: 0.5600000 
Iter:     3 | Cost: 1.1589332 | Acc train: 0.4533333 | Acc validation: 0.5600000 
Iter:     4 | Cost: 0.9806934 | Acc train: 0.4800000 | Acc validation: 0.5600000 
Iter:     5 | Cost: 0.8865623 | Acc train: 0.6133333 | Acc validation: 0.7600000 
Iter:     6 | Cost: 0.8580769 | Acc train: 0.6933333 | Acc validation: 0.7600000 
Iter:     7 | Cost: 0.8473132 | Acc train: 0.7200000 | Acc validation: 0.6800000 
Iter:     8 | Cost: 0.8177533 | Acc train: 0.7333333 | Acc validation: 0.6800000 
Iter:     9 | Cost: 0.8001100 | Acc train: 0.7466667 | Acc validation: 0.6800000 
Iter:    10 | Cost: 0.7681053 | Acc train: 0.8000000 | Acc validation: 0.7600000 
Iter:    11 | Cost: 0.7440015 | Acc train: 0.8133333 | Acc validation: 0.9600000 
Iter:    12 | Cost: 0.7583777 | Acc train: 0.6800000 | Acc validation: 0.7600000 
Iter:    13 | Cost: 0.7896372 | Acc train: 0.6533333 | Acc validation: 0.7200000 
Iter:    14 | Cost: 0.8397790 | Acc train: 0.6133333 | Acc validation: 0.6400000 
Iter:    15 | Cost: 0.8632423 | Acc train: 0.5733333 | Acc validation: 0.6000000 
Iter:    16 | Cost: 0.8693517 | Acc train: 0.5733333 | Acc validation: 0.6000000 
Iter:    17 | Cost: 0.8350625 | Acc train: 0.6266667 | Acc validation: 0.6400000 
Iter:    18 | Cost: 0.7966558 | Acc train: 0.6266667 | Acc validation: 0.6400000 
Iter:    19 | Cost: 0.7563381 | Acc train: 0.6800000 | Acc validation: 0.7600000 
Iter:    20 | Cost: 0.7315459 | Acc train: 0.7733333 | Acc validation: 0.8000000 
Iter:    21 | Cost: 0.7182359 | Acc train: 0.8800000 | Acc validation: 0.9600000 
Iter:    22 | Cost: 0.7339132 | Acc train: 0.8133333 | Acc validation: 0.7200000 
Iter:    23 | Cost: 0.7498571 | Acc train: 0.6533333 | Acc validation: 0.6400000 
Iter:    24 | Cost: 0.7593763 | Acc train: 0.6000000 | Acc validation: 0.6400000 
Iter:    25 | Cost: 0.7423832 | Acc train: 0.6800000 | Acc validation: 0.6400000 
Iter:    26 | Cost: 0.7361186 | Acc train: 0.7333333 | Acc validation: 0.6800000 
Iter:    27 | Cost: 0.7300201 | Acc train: 0.7600000 | Acc validation: 0.6800000 
Iter:    28 | Cost: 0.7360923 | Acc train: 0.6666667 | Acc validation: 0.6400000 
Iter:    29 | Cost: 0.7371325 | Acc train: 0.6533333 | Acc validation: 0.6400000 
Iter:    30 | Cost: 0.7234520 | Acc train: 0.7466667 | Acc validation: 0.6800000 
Iter:    31 | Cost: 0.7112089 | Acc train: 0.8133333 | Acc validation: 0.7600000 
Iter:    32 | Cost: 0.6923495 | Acc train: 0.9200000 | Acc validation: 0.9200000 
Iter:    33 | Cost: 0.6792510 | Acc train: 0.9200000 | Acc validation: 1.0000000 
Iter:    34 | Cost: 0.6756795 | Acc train: 0.9200000 | Acc validation: 0.9600000 
Iter:    35 | Cost: 0.6787274 | Acc train: 0.8933333 | Acc validation: 0.8000000 
Iter:    36 | Cost: 0.6938181 | Acc train: 0.7466667 | Acc validation: 0.6800000 
Iter:    37 | Cost: 0.6765225 | Acc train: 0.8266667 | Acc validation: 0.8000000 
Iter:    38 | Cost: 0.6708057 | Acc train: 0.8266667 | Acc validation: 0.8000000 
Iter:    39 | Cost: 0.6740248 | Acc train: 0.7600000 | Acc validation: 0.7200000 
Iter:    40 | Cost: 0.6835014 | Acc train: 0.6666667 | Acc validation: 0.6400000 
Iter:    41 | Cost: 0.6686811 | Acc train: 0.7333333 | Acc validation: 0.7200000 
Iter:    42 | Cost: 0.6217025 | Acc train: 0.8933333 | Acc validation: 0.8400000 
Iter:    43 | Cost: 0.6004307 | Acc train: 0.9066667 | Acc validation: 0.9200000 
Iter:    44 | Cost: 0.5910768 | Acc train: 0.9066667 | Acc validation: 0.9200000 
Iter:    45 | Cost: 0.5694770 | Acc train: 0.9200000 | Acc validation: 0.9600000 
Iter:    46 | Cost: 0.5588385 | Acc train: 0.9200000 | Acc validation: 0.9600000 
Iter:    47 | Cost: 0.5381473 | Acc train: 0.9466667 | Acc validation: 1.0000000 
Iter:    48 | Cost: 0.5205658 | Acc train: 0.9466667 | Acc validation: 1.0000000 
Iter:    49 | Cost: 0.5064983 | Acc train: 0.9466667 | Acc validation: 1.0000000 
Iter:    50 | Cost: 0.4969733 | Acc train: 0.9466667 | Acc validation: 1.0000000 
Iter:    51 | Cost: 0.4782257 | Acc train: 0.9466667 | Acc validation: 1.0000000 
Iter:    52 | Cost: 0.4536953 | Acc train: 0.9600000 | Acc validation: 1.0000000 
Iter:    53 | Cost: 0.4350300 | Acc train: 1.0000000 | Acc validation: 1.0000000 
Iter:    54 | Cost: 0.4272909 | Acc train: 0.9733333 | Acc validation: 0.9600000 
Iter:    55 | Cost: 0.4288929 | Acc train: 0.9466667 | Acc validation: 0.9200000 
Iter:    56 | Cost: 0.4261037 | Acc train: 0.9333333 | Acc validation: 0.9200000 
Iter:    57 | Cost: 0.4082663 | Acc train: 0.9466667 | Acc validation: 0.9200000 
Iter:    58 | Cost: 0.3698736 | Acc train: 0.9600000 | Acc validation: 0.9200000 
Iter:    59 | Cost: 0.3420686 | Acc train: 1.0000000 | Acc validation: 1.0000000 
Iter:    60 | Cost: 0.3253480 | Acc train: 1.0000000 | Acc validation: 1.0000000 
In [38]:
plt.figure()
cm = plt.cm.RdBu

# make data for decision regions
xx, yy = np.meshgrid(np.linspace(0.0, 1.5, 20), np.linspace(0.0, 1.5, 20))
X_grid = [np.array([x, y]) for x, y in zip(xx.flatten(), yy.flatten())]

# preprocess grid points like data inputs above
padding = 0.3 * np.ones((len(X_grid), 1))
X_grid = np.c_[np.c_[X_grid, padding], np.zeros((len(X_grid), 1))]  # pad each input
normalization = np.sqrt(np.sum(X_grid ** 2, -1))
X_grid = (X_grid.T / normalization).T  # normalize each input
features_grid = np.array(
    [get_angles(x) for x in X_grid]
)  # angles for state preparation are new features
predictions_grid = [variational_classifier(weights, bias, f) for f in features_grid]
Z = np.reshape(predictions_grid, xx.shape)

# plot decision regions
cnt = plt.contourf(
    xx, yy, Z, levels=np.arange(-1, 1.1, 0.1), cmap=cm, alpha=0.8, extend="both"
)
plt.contour(
    xx, yy, Z, levels=[0.0], colors=("black",), linestyles=("--",), linewidths=(0.8,)
)
plt.colorbar(cnt, ticks=[-1, 0, 1])

# plot data
plt.scatter(
    X_train[:, 0][Y_train == 1],
    X_train[:, 1][Y_train == 1],
    c="b",
    marker="o",
    edgecolors="k",
    label="class 1 train",
)
plt.scatter(
    X_val[:, 0][Y_val == 1],
    X_val[:, 1][Y_val == 1],
    c="b",
    marker="^",
    edgecolors="k",
    label="class 1 validation",
)
plt.scatter(
    X_train[:, 0][Y_train == -1],
    X_train[:, 1][Y_train == -1],
    c="r",
    marker="o",
    edgecolors="k",
    label="class -1 train",
)
plt.scatter(
    X_val[:, 0][Y_val == -1],
    X_val[:, 1][Y_val == -1],
    c="r",
    marker="^",
    edgecolors="k",
    label="class -1 validation",
)

plt.legend()
plt.savefig('simple_classifier.png')
plt.show()

Sources¶

  • Original code and blog
  • Xiong, Ng, and Hanzo 2021, Quantum Error Mitigation Relying on Permutation Filtering
  • Nakaji & Yamamoto, 2020, Expressibility of the alternating layered ansatz for quantum computation
  • Optimizer explanations and discussion in the context of deep learning
  • Nesterov Optimizer explanations
In [ ]: